package com.utc.cards.games.damedepique;

import jade.core.AID;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.utc.cards.Constants;
import com.utc.cards.common.jade.AbstractGameBehaviour;
import com.utc.cards.common.jade.Mapper;
import com.utc.cards.model.HostModel;
import com.utc.cards.model.card.Card;
import com.utc.cards.model.deck.Deck;
import com.utc.cards.model.game.Fold;
import com.utc.cards.model.game.GameStatus;
import com.utc.cards.model.game.Info;
import com.utc.cards.model.game.InfoType;
import com.utc.cards.model.player.Hands;
import com.utc.cards.model.player.IPlayer;
import com.utc.cards.table.jade.agent.gameAgent.GameAgent;

// écoute uniquement les cartes jouées par les joueurs
public class DameDePiqueGameBehaviour extends AbstractGameBehaviour
{

    private static final long serialVersionUID = 8354989627797871147L;
    private static Logger log = LoggerFactory.getLogger(DameDePiqueGameBehaviour.class);
    private Map<IPlayer, Deck> echange;
    private int tour = 1;
    private int manche = 1;
    private int indexCurrentPlayer;
    private int playerReady = 0;

    private MessageTemplate template = MessageTemplate.MatchConversationId("game");
    private MessageTemplate templateReady = MessageTemplate.MatchConversationId("ready");

    private enum DameDePiqueGameStep
    {
        INITIALISATION, STEP_3_CARDS_EXCHANGE, STEP_GAMING, STEP_SCORE, DETERMINATE_FIRST_PLAYER, DETERMINATE_WINNER_PLAYER, END_GAME, ATTENTE_RULE, SEND_START_GAME, WAIT_PLAYERS, WAIT_IAS
    }

    private boolean done = false;

    private DameDePiqueGameStep step = DameDePiqueGameStep.ATTENTE_RULE;

    public DameDePiqueGameBehaviour(GameAgent gameAgent)
    {
        super(gameAgent);
        initGame();

    }

    @Override
    public void action()
    {
        switch (step)
        {
        case ATTENTE_RULE:
        {
            log.debug("Attente agent rule...");
            if (agent.getModel().isRuleAvailable())
            {
                log.debug("Agent rule is available");
                step = DameDePiqueGameStep.SEND_START_GAME;
            }
            break;
        }
        case SEND_START_GAME:
        {
            log.debug("SENDing START_GAME message...");

            ACLMessage message = new ACLMessage(ACLMessage.INFORM);
            message.setConversationId("info");
            Info info = new Info(InfoType.GAME_START, null);
            try
            {
                message.setContent(Mapper.getObjectMapper().writeValueAsString(info));
                for (AID aid : agent.getModel().getPlayersMap().values())
                {
                    log.debug("Game start message send to: " + aid);
                    message.addReceiver(aid);
                }
                agent.send(message);

                agent.launchGameView();

                step = DameDePiqueGameStep.WAIT_PLAYERS;
            }
            catch (JsonProcessingException e)
            {
                log.error("Error while mapping Info to String");
                e.printStackTrace();
            }

            break;
        }
        case WAIT_PLAYERS:
        {
            log.debug("Game : WAIT_PLAYERS ...");
            ACLMessage message = agent.receive(templateReady);
            if (message != null)
            {
                playerReady++;
                log.debug("Game : WAIT_PLAYERS ... Ready " + message.getSender().getName());
                if (playerReady == (agent.getModel().getGame().getPlayers().size() - agent.getModel().getIaPlayerModels().size()))
                {
                    step = DameDePiqueGameStep.INITIALISATION;
                }
            }
            else
            {
                block();
            }
        }
            break;
        case WAIT_IAS:
        {
            log.debug("Game : WAIT_IAS ...");
            if (agent.getModel().isIAPlayerAgentsAvailable())
            {
                log.debug("Game : WAIT_IAS : all IA ready");
                step = DameDePiqueGameStep.INITIALISATION;
            }
            block(10);

            break;
        }
        case INITIALISATION:
        {
            log.debug("Game : INITIALISATION ...");

            // On vide l'historique des pli
            agent.getModel().setOldFolds(new Stack<Fold>());
            agent.getModel().setCurrentFold(new Fold());
            // On demande à l'agentRule de distribuer
            ACLMessage message = new ACLMessage(ACLMessage.INFORM);

            Info info = new Info();
            info.setType(InfoType.CARDS_DISTRIBUTION);

            try
            {
                message.setContent(Mapper.getObjectMapper().writeValueAsString(info));
            }
            catch (Exception e)
            {
                log.error("Game : INITIALISATION (mapping Info)");
                e.printStackTrace();
            }

            log.debug("Game : INITIALISATION asking distribution");

            message.setConversationId("info");
            message.addReceiver(new AID(Constants.CARDS_HOST_RULES_AGENT_NAME, AID.ISLOCALNAME));
            agent.send(message);
            //
            // // Attend que l'agent rules aie fini de distribuer
            // try
            // {
            // Thread.sleep(1000);
            // }
            // catch (InterruptedException e)
            // {
            // log.error("Game : INITIALISATION (Thread.sleep)");
            // e.printStackTrace();
            // }
            //
            // On réveille tous les joueurs
            for (final IPlayer player : agent.getModel().getGame().getPlayers())
            {
                agent.giveTurn(player);
            }
            step = DameDePiqueGameStep.STEP_3_CARDS_EXCHANGE;
            clearTabOfExchange();

            log.debug("Game : INITIALISATION -> STEP_3_CARDS_EXCHANGE");

        }
            log.debug("Game : INITIALISATION DONE");
            break;
        case STEP_3_CARDS_EXCHANGE:
        {
            // on attend une carte via un message
            ACLMessage msg = agent.receive(template);

            if (msg != null)
            {
                log.debug("Game : STEP_3_CARDS_EXCHANGE ... {}", msg.getSender().getLocalName());
                try
                {
                    IPlayer player = extractPlayer(msg);
                    Info info = Mapper.getObjectMapper().readValue(msg.getContent(), Info.class);

                    // log.debug("extractHands" + info.getJson());
                    Hands hands = Mapper.getObjectMapper().readValue(info.getJson(), Hands.class);

                    // Recupération de l'index du joueur
                    // int index = 0;
                    //
                    // for (IPlayer player2 : agent.getModel().getGame().getPlayers())
                    // {
                    // if (player2.getName().equals(player.getName()))
                    // break;
                    // else
                    // index++;
                    // }

                    log.debug("Game : STEP_3_CARDS_EXCHANGE ... {}", msg.getSender().getLocalName());

                    // On retire la carte envoyée du model
                    agent.getModel().getPlayerHands().get(player).get(IPlayer.MAIN_HAND).removeAll(hands.get(IPlayer.MAIN_HAND));

                    // Ajout de la carte au tableau echange
                    Deck deck = echange.get(player);
                    // Deck deck = echange.get(agent.getModel().getGame().getPlayers().get((index + manche) % 4));
                    deck.addAll(hands.get(IPlayer.MAIN_HAND));

                    // ///////
                    String cardsInfo = "";
                    for (Card card : deck)
                    {
                        cardsInfo += card.toString() + ",";
                    }
                    log.debug("Game : STEP_3_CARDS_EXCHANGE : {} ({})", msg.getSender().getLocalName(), cardsInfo);
                    // ///////

                    echange.put(player, deck);

                    // Si le joueur n'a pas encore envoyé ses 3 cartes on lui dit d'en renvoyer une
                    if (deck.size() < 3)
                    {
                        log.debug("Game : STEP_3_CARDS_EXCHANGE : {} ({}/3)", msg.getSender().getLocalName(), deck.size());
                        agent.giveTurn(player);
                    }

                    // quand toutes les cartes sont arrivées
                    // on réalise l'échange puis on peut attaquer la boucle de jeu
                    info = new Info();
                    if (allExchangeCardArrived())
                    {
                        log.debug("Game : STEP_3_CARDS_EXCHANGE : allExchangeCardArrived");
                        ACLMessage message = new ACLMessage(ACLMessage.INFORM);
                        message.setConversationId("info");
                        info.setType(InfoType.ADD_CARDS);

                        // On envoie les cartes à tout les joueurs
                        log.debug("Game : STEP_3_CARDS_EXCHANGE : exchanging cards");
                        for (IPlayer player3 : echange.keySet())
                        {

                            Map<String, Deck> hands2 = new HashMap<String, Deck>();
                            // ///////
                            String cardsInfo2 = "";
                            for (Card card : hands2.get(IPlayer.MAIN_HAND))
                            {
                                cardsInfo2 += card.toString() + ",";
                            }
                            log.debug("Game : STEP_3_CARDS_EXCHANGE : adding cards to {} ({})", player3.getName(), cardsInfo2);
                            // ///////
                            hands2.put(IPlayer.MAIN_HAND, echange.get(player3));
                            agent.addCardsToPlayer(hands2, player3);
                        }

                        // On demande a l'agent Rule le premier joueur
                        info.setType(InfoType.PLAYER_START);

                        message.setContent(Mapper.getObjectMapper().writeValueAsString(info));
                        message.addReceiver(new AID(Constants.CARDS_HOST_RULES_AGENT_NAME, AID.ISLOCALNAME));
                        agent.send(message);
                        log.debug("Game : STEP_3_CARDS_EXCHANGE : asking first player");

                        // donner le tour de jeu au premier joueur
                        step = DameDePiqueGameStep.DETERMINATE_FIRST_PLAYER;
                        agent.getModel().getGame().setStatus(GameStatus.IN_GAME);
                        log.debug("Game : STEP_3_CARDS_EXCHANGE -> DETERMINATE_FIRST_PLAYER");
                    }
                }
                catch (Exception e)
                {
                    log.error("Game : STEP_3_CARDS_EXCHANGE (mapping)");
                    e.printStackTrace();
                }
                log.debug("Game : STEP_3_CARDS_EXCHANGE DONE");
            }
            else
            {
                block();
            }
        }
            break;
        case DETERMINATE_FIRST_PLAYER:
        {
            ACLMessage msg = agent.receive(MessageTemplate.MatchConversationId("first-player"));
            if (msg != null)
            {
                log.debug("Game : DETERMINATE_FIRST_PLAYER ...");
                try
                {
                    String firstPlayerName = Mapper.getObjectMapper().readValue(msg.getContent(), String.class);
                    indexCurrentPlayer = 0;
                    for (IPlayer player : agent.getModel().getGame().getPlayers())
                    {
                        if (!player.getName().equals(firstPlayerName))
                        {
                            indexCurrentPlayer++;
                            log.debug("Game : DETERMINATE_FIRST_PLAYER indexCurrentPlayer++");
                        }
                        else
                        {
                            break;
                        }
                    }
                    agent.giveTurn(agent.getModel().getGame().getPlayerByName(firstPlayerName));
                    step = DameDePiqueGameStep.STEP_GAMING;
                    log.debug("Game : DETERMINATE_FIRST_PLAYER -> STEP_GAMING");
                }
                catch (Exception e)
                {
                    log.error("Game : DETERMINATE_FIRST_PLAYER (mapping String))");
                    e.printStackTrace();
                }
            }
            else
            {
                block();
            }
        }
            log.debug("Game : DETERMINATE_FIRST_PLAYER DONE");
            break;
        case STEP_GAMING:
        {
            ACLMessage msg = agent.receive(template);

            // on attend une carte
            if (msg != null)
            {
                log.debug("Game : STEP_GAMING ...");
                try
                {
                    IPlayer player = extractPlayer(msg);
                    Info info = Mapper.getObjectMapper().readValue(msg.getContent(), Info.class);

                    // log.debug("extractHands" + info.getJson());
                    Hands hands = Mapper.getObjectMapper().readValue(info.getJson(), Hands.class);

                    // la seule carte jouée
                    Card cardPlay = hands.get(IPlayer.MAIN_HAND).get(0);

                    // On retire la carte du model
                    player.getHand().remove(cardPlay);

                    // Ajout de la carte au pli
                    Deck deck = new Deck();
                    deck.add(cardPlay);
                    agent.getModel().getCurrentFold().setCards(player, deck);

                    // On notifie la vue
                    onCardPlayed();

                    // si tous les joueurs ont joué
                    if (agent.getModel().getCurrentFold().getFoldCards().size() == agent.getModel().getGame().getPlayers().size())
                    {
                        // On demande la détermination du gagnant
                        info = new Info();
                        ACLMessage message = new ACLMessage(ACLMessage.INFORM);
                        info.setType(InfoType.ESTABLISH_FOLD_WINNER);

                        message.setContent(Mapper.getObjectMapper().writeValueAsString(info));

                        message.addReceiver(new AID(Constants.CARDS_HOST_RULES_AGENT_NAME, AID.ISLOCALNAME));
                        agent.send(message);

                        // determiner le gagnant du pli
                        step = DameDePiqueGameStep.DETERMINATE_WINNER_PLAYER;
                        log.debug("Game : STEP_GAMING -> DETERMINATE_WINNER_PLAYER");
                    }
                    else
                    {
                        // On donne le tour au joueur suivant
                        indexCurrentPlayer = (indexCurrentPlayer + 1) % 4;
                        agent.giveTurn(agent.getModel().getGame().getPlayers().get(indexCurrentPlayer));
                    }

                }
                catch (Exception e)
                {
                    log.error("Game : STEP_GAMING (mapping)");
                    e.printStackTrace();
                }
            }
            else
            {
                block();
            }
        }
            log.debug("Game : STEP_GAMING DONE");
            break;
        case DETERMINATE_WINNER_PLAYER:
        {
            ACLMessage msg = agent.receive(MessageTemplate.MatchConversationId("winner-player"));

            // on le nom du gagnant du plie
            if (msg != null)
            {
                log.debug("Game : DETERMINATE_WINNER_PLAYER ...");
                try
                {
                    String winnerName = Mapper.getObjectMapper().readValue(msg.getContent(), String.class);
                    IPlayer player = agent.getModel().getGame().getPlayerByName(winnerName);
                    // On enregistre le winner
                    HostModel.Instance().getCurrentFold().setWinner(player);

                    // On notifie la vue
                    onFoldWon();

                    tour++;
                    // Si c'est le dernier tour
                    if (tour > 13)
                    {
                        tour = 1;
                        // On change de manche
                        manche++;
                        step = DameDePiqueGameStep.STEP_SCORE;
                        log.debug("Game : DETERMINATE_WINNER_PLAYER -> STEP_SCORE");

                        // Si c'était la dernière manche
                        if (manche > 4)
                        {
                            // Calcule des scores
                            manche = 1;
                        }
                    }
                    else
                    {
                        // On dit au gagnant du plis précedent du pli précedant de jouer
                        indexCurrentPlayer = 0;
                        for (IPlayer player2 : agent.getModel().getGame().getPlayers())
                        {
                            if (player2.equals(winnerName))
                                break;
                            else
                                indexCurrentPlayer++;
                        }
                        agent.giveTurn(player);
                        step = DameDePiqueGameStep.STEP_GAMING;
                        log.debug("Game : DETERMINATE_WINNER_PLAYER -> STEP_GAMING");
                    }
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }

            }
        }
            log.debug("Game : STEP_GAMING DONE");
            break;
        case STEP_SCORE:
        {
            log.debug("Game : STEP_SCORE ...");
            ACLMessage message = new ACLMessage(ACLMessage.INFORM);
            try
            {
                Info info = new Info();
                info.setType(InfoType.SCORE);
                message.setContent(Mapper.getObjectMapper().writeValueAsString(info));
                message.addReceiver(new AID(Constants.CARDS_HOST_RULES_AGENT_NAME, AID.ISLOCALNAME));
                agent.send(message);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }

            try
            {
                Thread.sleep(10);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            // On notifie la vue du nouveau score
            onScoreUpdated();

            for (Entry<String, Integer> score : agent.getModel().getScores().getLastPartyScore().entrySet())
            {
                if (score.getValue() > 100)
                {
                    log.debug("Game : STEP_GAMING -> END_GAME");
                    step = DameDePiqueGameStep.END_GAME;
                    break;
                }
            }

            if (step != DameDePiqueGameStep.END_GAME)
            {
                log.debug("Game : STEP_GAMING -> INITIALISATION");
                step = DameDePiqueGameStep.INITIALISATION;
            }
        }
            log.debug("Game : STEP_SCORE DONE");

            break;
        case END_GAME:
            log.debug("Game : END_GAME ...");
            onPartyOver();
            log.debug("Game : END_GAME DONE");

            break;
        default:
            log.error("DameDePiqueGameBehaviour state error : out of step");
            break;
        }
    }

    private void clearTabOfExchange()
    {
        echange.clear();
        for (IPlayer player : agent.getModel().getGame().getPlayers())
        {
            echange.put(player, new Deck());
        }

    }

    private IPlayer extractPlayer(ACLMessage msg)
    {
        IPlayer player = null;

        for (String name : agent.getModel().getPlayersMap().keySet())
        {
            if (agent.getModel().getPlayersMap().get(name).getLocalName().equals(msg.getSender().getLocalName()))
            {
                player = agent.getModel().getGame().getPlayerByName(name);
                break;
            }
        }
        log.debug("Extracted player : {}", player.getName());
        return player;
    }

    private void initGame()
    {
        agent.getModel().getGame().setStatus(GameStatus.PRE_GAME);
        tour = 1;
        manche = 1;
        echange = new HashMap<IPlayer, Deck>();
    }

    private boolean allExchangeCardArrived()
    {
        String exchInfo = "";
        for (Entry<IPlayer, Deck> player : echange.entrySet())
        {
            exchInfo += player.getValue().size() + "/";
        }
        log.debug("Exchange status : {}", exchInfo);
        for (Entry<IPlayer, Deck> player : echange.entrySet())
        {
            if (player.getValue().size() < 3)
            {
                return false;
            }

        }
        return true;
    }

    @Override
    public boolean done()
    {
        return done;
    }

}
